Skip to content

Conversation

@lixiaoyan
Copy link
Contributor

@lixiaoyan lixiaoyan commented Dec 10, 2025

This allows us to properly place a dialog in a horizontally scrolled page (document.scrollingElement.scrollLeft !== 0).

<ModalOverlay className="absolute top-0 left-0 h-(--page-height) w-(--page-width)">
  <Modal className="grid place-items-center sticky top-0 left-0 h-(--visual-viewport-height) w-(--visual-viewport-width)">
    <Dialog />
  </Modal>
</ModalOverlay>

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

🧢 Your Project:

@lixiaoyan lixiaoyan changed the title feat(RAC): expose --page-width and --visual-viewport-width from M… feat(RAC): expose --page-width and --visual-viewport-width from Modal Dec 10, 2025
@lixiaoyan lixiaoyan force-pushed the patch-6 branch 2 times, most recently from 09625c8 to 677c5c5 Compare December 10, 2025 11:18
snowystinger
snowystinger previously approved these changes Dec 10, 2025
Copy link
Member

@snowystinger snowystinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems reasonable

@lixiaoyan
Copy link
Contributor Author

@devongovett Hi, any chance to get this merged before next release? This is necessary for our project.

@snowystinger
Copy link
Member

@lixiaoyan I'll ask the team, but just to set realistic expectations, it's unlikely to be added this release. We're just a little too late in the cycle with our main priorities on S2 going to 1.0 and our new docs site. I think I can say we would all think this is fairly low risk because it matches the equivalent for height, but it would still need to be discussed and tested. You can use it with a yarn patch or something equivalent in the meantime. Thank you for understanding.

@snowystinger
Copy link
Member

I brought it up, the team judged it was just too close to release to take this in.

In addition, we were curious if you had tried using 100vw and if so, what part didn't work for you? An example would be useful to show the use case for this.

@snowystinger snowystinger added the waiting Waiting on Issue Author label Dec 16, 2025
@rothsandro
Copy link

Using 100vw (and likely also --page-width) for the modal overlay width results in a horizontal scrollbar during the animation, if the page has a vertical scrollbar (that is always visible and not just on scroll). This can be reproduced in the RAC docs:

modal

@snowystinger snowystinger removed the waiting Waiting on Issue Author label Dec 17, 2025
@snowystinger
Copy link
Member

Using 100vw (and likely also --page-width) for the modal overlay width results in a horizontal scrollbar during the animation, if the page has a vertical scrollbar (that is always visible and not just on scroll). This can be reproduced in the RAC docs:

I only see the scrollbars the first time I close a Modal, after that it's fine. Given that it has 100vw both times, it doesn't seem like that is the culprit?

@rothsandro
Copy link

I only see the scrollbars the first time I close a Modal, after that it's fin

Hm, for me it happens always. The new docs page doesn't have a vertical scrollbar but after closing the dialog a vertical scrollbar appears and stays there, and a horizontal scrollbar appears during the animation. The old docs page already had a vertical scrollbar, the issue there was just a temporary horizontal scrollbar during the animation.

Adobe Spectrum does have this problem (and doesn't use 100vw).

modal-scrollbar.mp4

@snowystinger
Copy link
Member

snowystinger commented Dec 17, 2025

Odd, now I'm incapable of reproducing in Chrome or Safari or Firefox. Neither Modal or Dialog docs pages in either library
Chrome 143.0.7499.41
Safari 26.1
Firefox 146

@rothsandro
Copy link

If you are on macOS, did you enable "Show scrollbar: always" in the OS settings? My screenshot is from macOS with this setting enabled, the video is from Windows (which always shows scrollbars, not just on scroll).

@lixiaoyan
Copy link
Contributor Author

Anyhow, the --page-width does not have any equivalents.

@snowystinger
Copy link
Member

If you are on macOS, did you enable "Show scrollbar: always" in the OS settings? My screenshot is from macOS with this setting enabled, the video is from Windows (which always shows scrollbars, not just on scroll).

Sorry, I should have specified, I always have scrollbars on. I dislike hiding them :)

Anyhow, the --page-width does not have any equivalents.

But what do you need it for? Can you provide an example which demonstrates what you'd use it for? I know there's a bit of an example in the description, but I unfortunately may make different assumptions about setup.

@rothsandro
Copy link

Oh, just found out it's caused by the 1Password browser extension, so nothing you have to worry about. I should have tested this in a clean browser profile 😕 (And my comment above is wrong, I meant it does not happen in Adobe Spectrum, that's why assume it's somehow related to the 100vw). Sorry for the interruption here.

@lixiaoyan
Copy link
Contributor Author

lixiaoyan commented Dec 18, 2025

@snowystinger

The backdrop (ModalOverlay) should cover everything below. We need --page-width to specify its width.

image

@lixiaoyan
Copy link
Contributor Author

In addition, we were curious if you had tried using 100vw and if so, what part didn't work for you? An example would be useful to show the use case for this.

In our project, we use this to implement the drawer component:

<ModalOverlay className="absolute top-0 left-0 h-(--page-height) w-(--page-width)">
  <Modal className="grid justify-items-end sticky top-0 left-0 h-(--visual-viewport-height) w-(--visual-viewport-width)">
    <Dialog />
  </Modal>
</ModalOverlay>

If we use 100vw instead, the drawer will exceed the viewport and have its right side clipped.

image

@snowystinger
Copy link
Member

I'm really sorry, but I'm not understanding those pictures. Would you mind taking our Tailwind Starter https://react-aria.adobe.com/getting-started#storybook-starter-kits and modifying that to show the problem then pushing it to Github and sharing it here? That way we can run it and it'll be as close to your setup as possible.

Otherwise, I need some really thorough instructions on how to get to those images.

Some things to keep in mind. 100vw doesn't take into account scrollbars, but 100% will, so as long as you have width: 100% on all the parents leading up to the html document, it shouldn't be affected in that way.

It's worth noting that the reason for page-height and viewport-height variables was specifically due to iOS nav bars, which only affect height.

@lixiaoyan
Copy link
Contributor Author

lixiaoyan commented Dec 19, 2025

@snowystinger https://github.com/lixiaoyan/rac-modal-horizontal-scroll

Screen.Recording.2025-12-19.at.9.46.37.PM.mov
100% 100vw --page-width and --visual-viewport-width
Document is scrolled to the start Screenshot 2025-12-19 at 3 23 21 PM Screenshot 2025-12-19 at 3 23 26 PM Screenshot 2025-12-19 at 3 23 30 PM
Document is scrolled at the center Screenshot 2025-12-19 at 3 23 36 PM Screenshot 2025-12-19 at 3 23 39 PM Screenshot 2025-12-19 at 3 23 41 PM

However, there is one remaining issue with the new approach offered in this PR: the scrollbar gutter clips the drawer's right side. It's related to a CSS spec issue: w3c/csswg-drafts#8099. Chrome includes the scrollbar gutter's width in the visual viewport width, while Firefox and Safari do not. #9199 has the same root cause.

As I suggested in #9199 (comment), we may need a new util to calculate the visual viewport size without the scorllbar.

const width = Math.min(visualViewport.width, document.documentElement.clientWidth);
const height = visualViewport.height;

Edited: document.documentElement.clientWidth does not work either for stable scrollbar gutters. ref

@lixiaoyan
Copy link
Contributor Author

lixiaoyan commented Dec 19, 2025

Some things to keep in mind. 100vw doesn't take into account scrollbars, but 100% will, so as long as you have width: 100% on all the parents leading up to the html document, it shouldn't be affected in that way.

The <ModalOverlay> is an absolute-positioned element rather than a fixed one. When a document is horizontally scrolled, it might be outside the viewport. An element cannot be absolute in the y-axis and fixed in the x-axis simultaneously.

To quickly reproduce, goto https://react-spectrum.adobe.com/Dialog and set the width: 200vw on the <body>. Scroll the document horizontally, then click the "Open Dialog" button.

@lixiaoyan
Copy link
Contributor Author

lixiaoyan commented Dec 19, 2025

After some investigations, I discovered that, regardless of iOS, even when using position: fixed to place the ModalOverlay, some tricks are still necessary. Prior to Chrome 136, with scrollbar-gutter: stable; on <html>, an element with position: fixed; inset: 0; would extend to the scrollbar gutter area, resulting in anything on the right side being clipped. It was just fixed earlier this year; it's worth having a workaround.
https://bugs.chromium.org/p/chromium/issues/detail?id=1251856

image

^ Screenshot of https://jsbin.com/vefumujapa/1/edit?html,css,js,output with an older version Chrome


let visualViewport = typeof document !== 'undefined' && window.visualViewport;

export function useViewportSize(): ViewportSize {
Copy link
Contributor Author

@lixiaoyan lixiaoyan Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might rename this useVisualViewportSize because it returns the visual viewport size (does not include the scrollbar) but not the 100vw one.

@lixiaoyan
Copy link
Contributor Author

@devongovett WDYT?

@lixiaoyan
Copy link
Contributor Author

Tested with https://interop-2022-viewport.netlify.app/

is the scrollbar width "excluded"? scrollbar is present stable scrollbar gutter
(scrollbar is not present)
100vw/100dvw
position: absolute and width: 100%
position: fixed and width: 100% Chrome: ⚠️, Others: ✅
visualViewport.width Chrome: ❌, Others: ✅
documentElement.clientWidth Chrome: ❌, Others: ✅
documentElement.getBoundingClientRect().width
ICB (initial containing block) width

// The visual viewport width may include the scrollbar gutter. We should use the minimum width between
// the visual viewport and the document element to ensure that the scrollbar width is always excluded.
// See: https://github.com/w3c/csswg-drafts/issues/8099
? Math.min(visualViewport.width * visualViewport.scale, document.documentElement.clientWidth)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, this line still cannot get the correct ICB width. We may need to insert an empty <div> with position: absolute; width: 100% inside <body> and use its clientWidth for the ICB width. document.documentElement.getBoundingClientRect().width may also work, as long as no width style applied to the <html>.

@lixiaoyan
Copy link
Contributor Author

lixiaoyan commented Dec 19, 2025

Another option is to use something like left-(--page-scroll-left) on the <ModalOverlay>, although it looks a little strange.

<ModalOverlay className="absolute top-0 left-(--page-scroll-left) h-(--page-height) w-full">
  <Modal className="grid justify-items-end sticky top-0 h-(--visual-viewport-height) w-full">
    <Dialog />
  </Modal>
</ModalOverlay>

Edited: I filled a new PR with the method described above. #9383

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants